# coding=utf-8
"""
author: jamon
"""
import inspect
import sys
import traceback

from .espoir_log import global_log


# 记录reload过的class, func
_reloaded_objs = set()

_ignore_attrs = {
    '__module__',
    '_reload_all',
    '__dict__',
    '__weakref__',
    '__doc__',
}

# 热更前老模块的信息
_old_module_info = {}

# 热更后要执行的操作
_after_reload = []


def is_cython_function(func):
    return type(func).__name__ == "cython_function_or_method"


def is_python_function(func):
    return inspect.isfunction(func)


def is_function(func):
    return is_cython_function(func) or is_python_function(func)


def need_reload_obj(obj):
    global _reloaded_objs
    return obj not in _reloaded_objs


def get_need_reload_module():
    modules: dict = {}
    _old_modules = dict(sys.modules)
    for name, module in _old_modules.items():
        if inspect.ismodule(module):
            if "_need_reload" in module.__dict__:
                modules[name] = module

    return modules


def start_reload():
    global _after_reload
    global _reloaded_objs
    global _old_module_info
    need_modules = get_need_reload_module()
    print("start_reload", need_modules)
    _after_reload = []
    for name, _ in need_modules.items():
        sys.modules.pop(name)

    for name, module_info in need_modules.items():
        try:
            __import__(name)
        except ImportError:
            sys.modules[name] = need_modules[name]
            global_log.error("reload unchanged module",  name=name)
        except Exception:   # noqa
            sys.modules[name] = need_modules[name]
            global_log.error("reload error occurs while import", name=name, old_modules=need_modules[name])
            global_log.error(traceback.format_exc())
        update_module(_old_module_info, sys.modules[name])

    for listener in _after_reload:
        try:
            listener()
        except Exception:   # noqa
            global_log.error(traceback.format_exc())


def update_module(old_attrs, module):
    global _after_reload
    for name, attr in inspect.getmembers(module):
        if isinstance(attr, type) and attr is not type:
            old_class = old_attrs.get(name)
            if old_class:
                if need_reload_obj(attr):
                    update_class(old_class, attr)
                    _reloaded_objs.add(old_class)
                # 仍然使用old_class(确保isinstance之类的判断正确)
                setattr(module, name, old_class)
        elif is_function(attr):
            old_fun = old_attrs.get(name)
            if is_function(old_fun):
                if not need_reload_obj(old_fun):
                    continue
                if not update_function(old_fun, attr):
                    old_attrs[name] = attr
                else:
                    setattr(module, name, old_fun)
                    _reloaded_objs.add(old_fun)

    if hasattr(module, "__on_after_reload__"):
        _after_reload.append(module.__on_after_reload__)


def update_function(old_func, new_func, update_cell_depth=3):
    """

    :param old_func:
    :param new_func:
    :param update_cell_depth:  int, closure扫描深度
    :return: True更新old_func成功, False更新old_func失败
    """
    if inspect.isbuiltin(old_func) or inspect.isbuiltin(new_func):
        return True
    # 非builtin的除了更新函数本身的属性, 还要更新closure里面的
    old_cell_num = 0
    new_cell_num = 0
    if old_func.func_closure:
        old_cell_num = len(old_func.func_closure)
    if new_func.func_closure:
        new_cell_num = len(new_func.func_closure)
    if old_cell_num != new_cell_num:
        return False
    if is_python_function(old_func) and is_cython_function(new_func):
        # python函数替换成cython函数
        return False
    try:
        setattr(old_func, 'func_code', new_func.func_code)
        setattr(old_func, 'func_defaults', new_func.func_defaults)
        setattr(old_func, 'func_doc', new_func.func_doc)
        setattr(old_func, 'func_dict', new_func.func_dict)
    except AttributeError:
        # cython函数替换成其他函数
        global_log.error(traceback.format_exc())
    if not (update_cell_depth and old_cell_num):
        return True
    for index, cell in enumerate(old_func.func_closure):
        if is_function(cell.cell_contents):
            return update_function(cell.cell_contents,
                                   new_func.func_closure[index].cell_contents,
                                   update_cell_depth - 1)
    return True


def update_class(old_class, new_class):
    for name, old_attr in old_class.__dict__.items():
        # 删除函数
        if name in new_class.__dict__ or not is_function(old_attr):
            continue
        type.__delattr__(old_class, name)

    # 修改
    for name, new_attr in new_class.__dict__.items():
        # 新增属性
        if name not in old_class.__dict__:
            setattr(old_class, name, new_attr)
            continue
        old_attr = old_class.__dict__[name]
        if is_function(old_attr) and is_function(new_attr):
            # 如果更新函数失败就替换成新的
            if not update_function(old_attr, new_attr):
                setattr(old_class, name, new_attr)

        elif isinstance(new_attr, (staticmethod, classmethod)):
            if not update_function(old_attr.__func__, new_attr.__func__):
                try:
                    old_attr.__func__ = new_attr.__func__
                except TypeError as e:
                    if "readonly" in e.message:
                        setattr(old_class, name, new_attr)
                        continue
                    raise
        elif name not in _ignore_attrs:
            # 全部用新的
            setattr(old_class, name, new_attr)


